import sys, getopt
import urllib2
import threading
import time
from httplib import BadStatusLine

#############################
# presentation
#############################
# 
# httpfuzz.py can fuzz http headers.
#
#
# licence New Free BSD 2010.
#
# To test it:
# # python -m SimpleHTTPServer 80
#

#############################
# fuzz patterns
#############################

PATTERNLIST = [
# simple bof
['"A"*5000' , "A"*5000 ],
# strings ripped from sulley
['"/.:/"  + "A"*5000 + "\x00\x00"' , "/.:/"  + "A"*5000 + "\x00\x00" ],
['"/.../" + "A"*5000 + "\x00\x00"' , "/.../" + "A"*5000 + "\x00\x00" ],
['"/.../.../.../.../.../.../.../.../.../.../"' , "/.../.../.../.../.../.../.../.../.../.../" ],
['"/../../../../../../../../../../../../etc/passwd"' , "/../../../../../../../../../../../../etc/passwd" ],
['"/../../../../../../../../../../../../boot.ini"' , "/../../../../../../../../../../../../boot.ini" ],
['"..:..:..:..:..:..:..:..:..:..:..:..:..:"' , "..:..:..:..:..:..:..:..:..:..:..:..:..:" ],
['"\\\\*"' , "\\\\*" ],
['"\\\\?\\"' , "\\\\?\\" ],
['"/\\" * 5000' , "/\\" * 5000 ],
['"/." * 5000' , "/." * 5000 ],
['"!@#$%%^#$%#$@#$%$$@#$%^^**(()"' , "!@#$%%^#$%#$@#$%$$@#$%^^**(()" ],
['"%01%02%03%04%0a%0d%0aADSF"' , "%01%02%03%04%0a%0d%0aADSF" ],
['"%01%02%03@%04%0a%0d%0aADSF"' , "%01%02%03@%04%0a%0d%0aADSF" ],
['"/%00/"' , "/%00/" ],
['"%00/"' , "/%00/" ],
['"%00"' , "%00" ],
['"%u0000"' , "%u0000" ],
['"%\xfe\xf0%\x00\xff"' , "%\xfe\xf0%\x00\xff" ],
['"%\xfe\xf0%\x01\xff" * 20' , "%\xfe\xf0%\x01\xff" * 20 ],

# format strings.
['"%n"     * 100' , "%n"     * 100 ],
['"%n"     * 500' , "%n"     * 500 ],
['"\"%n\"" * 500' , "\"%n\"" * 500 ],
['"%s"     * 100' , "%s"     * 100 ],
['"%s"     * 500' , "%s"     * 500 ],
['"\"%s\"" * 500' , "\"%s\"" * 500 ],

# command injection.
['"|touch /tmp/shatter"' , "|touch /tmp/shatter" ],
['";touch /tmp/shatter;"' , ";touch /tmp/shatter;"],
['"|notepad"' , "|notepad" ],
['";notepad;"' , ";notepad;" ],
['"\nnotepad\n"' , "\nnotepad\n" ],

# SQL injection.
['"1;SELECT%20*"' , "1;SELECT%20*" ],
["\"'sqlattempt1\"", "'sqlattempt1" ],
['"(sqlattempt2)"', "(sqlattempt2)" ],
['"OR%201=1"' , "OR%201=1" ],

# some binary strings.
['"\xde\xad\xbe\xef"' , "\xde\xad\xbe\xef" ],
['"\xde\xad\xbe\xef" * 10' , "\xde\xad\xbe\xef" * 10 ],
['"\xde\xad\xbe\xef" * 100' , "\xde\xad\xbe\xef" * 100 ],
['"\xde\xad\xbe\xef" * 1000' , "\xde\xad\xbe\xef" * 1000 ],
['"\xde\xad\xbe\xef" * 10000' , "\xde\xad\xbe\xef" * 10000 ],
['"\x00"             * 1000' , "\x00"             * 1000 ],

# miscellaneous.
['"\r\n" * 100' , "\r\n" * 100 ],
['"<>" * 500' , "<>" * 500 ]         # sendmail crackaddr (http://lsd-pl.net/other/sendmail.txt)
]

#############################
# header list
#############################

HEADERLIST = ['Accept','Accept-Charset','Accept-Encoding','Accept-Language','Authorization','Content-Encoding','Content-Language','Content-Length','Content-Type','Date','Forwarded','From','Link','Orig-URL','Referer','User-Agent']

'''
Accept	 		Type de contenu accepte par le browser (par exemple text/html). Voir types MIME
Accept-Charset	 	Jeu de caracteres attendu par le browser
Accept-Encoding	 	Codage de donnees accepte par le browser
Accept-Language	 	Langage attendu par le browser (anglais par defaut)
Authorization	 	Identification du browser aupres du serveur
Content-Encoding	Type de codage du corps de la requete
Content-Language	Type de langage du corps de la requete
Content-Length	 	Longueur du corps de la requete
Content-Type	 	Type de contenu du corps de la requete (par exemple text/html). Voir types MIME
Date	 		Date de debut de transfert des donnees
Forwarded	 	Utilise par les machines intermediaires entre le browser et le serveur
From	 		Permet de specifier l'adresse e-mail du client
Link	 		Relation entre deux URL
Orig-URL	 	URL d'origine de la requete
Referer	 		URL du lien a partir duquel la requete a ete effectuee
User-Agent	 	Chaine donnant des informations sur le client, comme le nom et la version du navigateur, du systeme d'exploitation
'''

#############################
# constants
#############################

MAXTHREADS = 400
MINTHREADS = 1

#############################
# default
#############################

global REMOTEIP
global REMOTEPORT
global URL
global THREADS
global semaphore
REMOTEIP = '127.0.0.1'
REMOTEPORT = 80
URL = 'http://127.0.0.1:80/'
THREADS = 10

#############################
# functions
#############################

# request ( pattern, url, headers )
# ex: request( '"a"*500', ('"a"*500',"a"*500), (('User-agent','Mozilla'),('Content-Type','aaa')) )
# returns server response
def request(p,d,h):
	try:
		global nb_of_responses
		global lock
		global semaphore
		global URL
		req = urllib2.Request(URL)
		if d:
			req.add_data(d[1])
		for i,head in enumerate(h):
			req.add_header(head[0],head[1])
		enum = threading.enumerate()
		f=urllib2.urlopen(req)
		lock.acquire()
		nb_of_responses += 1
		lock.release()
		# release semaphore. Main() can then create a new thread
		semaphore.release()
		print '[' +str(nb_of_responses) + '] '+ p + '. (Threads: ' + str(len(enum)) + ').'
		return 0
	except urllib2.URLError,e:
#		print '[' +str(nb_of_responses) + '] '+ p + '. (Threads: ' + str(len(enum)) + '). Server returns ' + str(e)
		pass 
	except BadStatusLine,e:
		print 'pb with ' + p + ' BadStatusLine exception. Holding on...'
		pass
	except KeyboardInterrupt:
		print 'KeyboardInterrupt catched. Quitting all threads'
		raise
		sys.exit(2)
	except ValueError:
		pass
	
# optimize
def optimize():
	global lock
	global nb_of_responses
	global semaphore
	global URL
	semaphore = threading.BoundedSemaphore(value = MAXTHREADS)
	best_nb_of_threads = 1
	best_ratio = 0
	lock = threading.Lock()
	nb_of_responses = 0
	# test connexion one time
	semaphore.acquire()
	request(None,())
	# test 20, 30, 40 ... threads
	for nb_of_threads_tested in range(MINTHREADS,MAXTHREADS, (MAXTHREADS-MINTHREADS)//10 ):
		nb_of_responses = 0
		begin = time.time()
		# start threads
		for j in range(nb_of_threads_tested):
			semaphore.acquire()
			current_thread = threading.Thread(None,request,None,((),),None)
			current_thread.start()
		# wait for threads end
		e = threading.enumerate()
		e.remove(threading.currentThread())
		for t in e:
			t.join()
		# conclude
		time_spent = time.time() - begin
		ratio = time_spent / nb_of_threads_tested
		if ratio > best_ratio:
			best_nb_of_threads = nb_of_threads_tested
			best_ratio = ratio
			print '[+]' + str(nb_of_threads_tested) + ' threads'

		else:
			print '[-]' + str(nb_of_threads_tested) + ' threads'
	return best_nb_of_threads


# fuzz
def fuzz():
	global semaphore
	global lock
	global nb_of_responses
	global URL
	semaphore = threading.BoundedSemaphore(value = THREADS)
	lock = threading.Lock()
	nb_of_responses = 0
	# test connexion one time
	semaphore.acquire()
	request('test',None,())
	# start THREADS threads
	# fuzz data in GET request
	save_URL = URL
	for pattern in PATTERNLIST:
		semaphore.acquire()
		URL = URL + str(pattern[1])
		current_thread = threading.Thread(None,request,None,( 'GET with ' + pattern[0] + ' in URL', None,() ),None)
		current_thread.start()
	URL = save_URL
	# fuzz data in POST request
	for pattern in PATTERNLIST:
		semaphore.acquire()
		current_thread = threading.Thread(None,request,None,( 'POST with ' + pattern[0] + ' in body', pattern,() ),None)
		current_thread.start()
	# fuzz headers
	for pattern in PATTERNLIST: 
		for header in HEADERLIST:
			semaphore.acquire()
			current_thread = threading.Thread(None,request,None,( 'GET with header ' + header + ' = ' + pattern[0], None,((header,pattern[1]),) ),None)
			current_thread.start()

		
# main	
def main():
	global URL
	global REMOTEPORT
	global REMOTEIP
	global THREADS
	global semaphore
	try:
		opts, args = getopt.getopt(sys.argv[1:], "hop:i:t:", ["help", "optimize", "port=", "ip=", "threads="])
	except getopt.GetoptError:
		# print help information and exit:
		usage()
		sys.exit(2)
	# options
	for o, arg in opts:
		if o in ("-p", "--port"):
			try:
				if 0 <= int(arg) <= 65535:
					REMOTEPORT = int(arg)
					URL = 'http://' + str(REMOTEIP) + ':' + str(REMOTEPORT) + '/'
		 	except ValueError:
				print 'Error: port must be an integer between 1 and 65535. Exiting now.'
				sys.exit(2)
		if o in ("-i", "-ip="):
			parts = arg.split(".")
			if len(parts) != 4:
				print 'Error: IP must be valid. Ex: 127.0.0.1. Exiting now.'
				sys.exit(2)
			for item in parts:
				if not 0 <= int(item) <= 255:
					print 'Error: IP must be valid. Ex: 127.0.0.1. Exiting now.'
					sys.exit(2)
			REMOTEIP = str(arg)
			URL = 'http://' + str(REMOTEIP) + ':' + str(REMOTEPORT) + '/'
		if o in ("-t", "threads="):
			try:
				int(arg)
			except:
				print 'Error: threads must be an integer. Ex: 100. Exiting now.'
				sys.exit(2)
			if int(arg) <= 0:
				print 'Error: threads must be a positive. Ex: 100. Exiting now.'
				sys.exit(2)
			THREADS = int(arg)
	# actions

	for o, arg in opts:
		if o in ("-h", "--help"):
			usage()
			sys.exit()
		if o in ("-o", "--optimize"):
			print 'best number of threads found is: ' + str(optimize())
			sys.exit()
		else:
			fuzz()
		
# usage		
def usage():
	print "httpfuzz.py fuzz http request against a remote server"
	print "usage: python httpfuzz.py [fuzz] [-h] [-o] [-i <IP> default 127.0.0.1] [-p <port> default:80]"
	print "    -h --help  : print help"
	print "    -o --optimize  : returns best number of threads (no fuzzing)"
	print "    -p <port> --port=80 remote port number. Default is 80"
	print "    -i <IP> --ip='127.0.0.1' remote server IP. Default is 127.0.0.1"
	print "    -t <nb of threads> --threads=10 number of threads started simultaneously"
	print "ex: python httpfuzz.py -i 127.0.1.1 -p 8080"
	print "ex: python httpfuzz.py -o -i 192.168.1.12 -p 443"




# let's go
if __name__ == "__main__":
	main()

